﻿
Imports std

Public Class rcserve
    Private WithEvents server As tcpServer2
    Private invokers As map(Of String, invokerType)

    Public Delegate Function invokeDelegate(ByVal arg As fromBytes) As toBytes
    Public Delegate Function invokeDelegate2(ByVal arg As fromBytes) As toBytes()
    Public Delegate Function invokeDelegate3(ByVal args() As fromBytes) As toBytes
    Public Delegate Function invokeDelegate4(ByVal args() As fromBytes) As toBytes()

    Public Function health() As Boolean
        Return server.health()
    End Function

    Private Class invokerType
        Public argType As Type
        Public invoke As invokeDelegate = Nothing
        Public invoke2 As invokeDelegate2 = Nothing
        Public invoke3 As invokeDelegate3 = Nothing
        Public invoke4 As invokeDelegate4 = Nothing
    End Class

    Private Function start() As Boolean
        Return server.start()
    End Function

    Public Function start(ByVal port As Int32) As Boolean
        server.port = port
        Return start()
    End Function

    Private Function registerInvokeImpl(Of argType) _
                                        (ByVal funcname As String, ByVal invoke As [Delegate] _
                                         , ByVal invokeIndex As Int32) As Boolean
        If isemptystring(funcname) Then
            raiseError("cannot register function with no name.", errorHandle.errorType.warning)
            Return False
        End If
        If invoke Is Nothing Then
            raiseError("invoke function is nothing.", errorHandle.errorType.warning)
            Return False
        End If
        If invokers.find(funcname) <> invokers.end() Then
            raiseError("caught an existing function named " + funcname + ", cannot register with same name." _
                       , errorHandle.errorType.warning)
            Return False
        End If

        If isdebugmode() Then
            raiseError("register removecall invoke, name " + funcname + ", invoke method " + getInvokeMethodName(invoke))
        End If

        Dim invoker As invokerType = Nothing
        invoker = New invokerType()
        invoker.argType = GetType(argType)
        Select Case invokeIndex
            Case 1
                invoker.invoke = invoke
            Case 2
                invoker.invoke2 = invoke
            Case 3
                invoker.invoke3 = invoke
            Case 4
                invoker.invoke4 = invoke
            Case Else
                assert(False, "should not have other invoke index.")
        End Select

        Return invokers.insert(funcname, invoker) <> invokers.end()
    End Function

    Public Function registerInvoke(Of argType As fromBytes) _
                                  (ByVal funcname As String, ByVal invoke As invokeDelegate) As Boolean
        Return registerInvokeImpl(Of argType)(funcname, invoke, 1)
    End Function

    Public Function registerInvoke(Of argType As fromBytes) _
                                  (ByVal funcname As String, ByVal invoke As invokeDelegate2) As Boolean
        Return registerInvokeImpl(Of argType)(funcname, invoke, 2)
    End Function

    Public Function registerInvoke(Of argType As fromBytes) _
                                  (ByVal funcname As String, ByVal invoke As invokeDelegate3) As Boolean
        Return registerInvokeImpl(Of argType)(funcname, invoke, 3)
    End Function

    Public Function registerInvoke(Of argType As fromBytes) _
                                  (ByVal funcname As String, ByVal invoke As invokeDelegate4) As Boolean
        Return registerInvokeImpl(Of argType)(funcname, invoke, 4)
    End Function

    Public Function removeInvoke(ByVal funcname As String) As Boolean
        Return invokers.erase(funcname)
    End Function

    Public Sub clearInvokes()
        invokers.clear()
    End Sub

    Public Sub New()
        server = New tcpServer2()
        invokers = New map(Of String, invokerType)()
    End Sub

    Public Sub [stop](Optional ByVal waittime As Int64 = maxInt64)
        server.stop(waittime)
    End Sub

    Private Sub server_receive(ByVal handle As tcpServer.talkHandle) Handles server.receive
        Dim funcname As String = Nothing
        Dim rtnData As nettransData = Nothing
        rtnData = New nettransData()

        If protocol.getname(handle.receiveData, funcname) Then
            Dim invoker As map(Of String, invokerType).iterator = Nothing
            invoker = invokers.find(funcname)
            If invoker <> invokers.end() Then
                With (+invoker).second
                    Dim bt() As fromBytes = Nothing
                    If protocol.getarguments(handle.receiveData, bt, .argType) Then
                        Dim rtns() As toBytes = Nothing
                        If Not .invoke Is Nothing Then
                            Try
                                ReDim rtns(0)
                                If bt Is Nothing OrElse bt.Length = 0 Then
                                    rtns(0) = .invoke(Nothing)
                                Else
                                    rtns(0) = .invoke(bt(0))
                                End If
                            Catch ex As Exception
                                raiseError("cannot invoke to registered function " + getInvokeMethodName(.invoke) _
                                           + ", " + ex.Message, errorHandle.errorType.warning)
                            End Try
                        ElseIf Not .invoke2 Is Nothing Then
                            Try
                                If bt Is Nothing OrElse bt.Length = 0 Then
                                    rtns = .invoke2(Nothing)
                                Else
                                    rtns = .invoke2(bt(0))
                                End If
                            Catch ex As Exception
                                raiseError("cannot invoke to registered function " + getInvokeMethodName(.invoke2) _
                                           + ", " + ex.Message, errorHandle.errorType.warning)
                            End Try
                        ElseIf Not .invoke3 Is Nothing Then
                            Try
                                ReDim rtns(0)
                                rtns(0) = .invoke3(bt)
                            Catch ex As Exception
                                raiseError("cannot invoke to registered function " + getInvokeMethodName(.invoke3) _
                                           + ", " + ex.Message, errorHandle.errorType.warning)
                            End Try
                        ElseIf Not .invoke4 Is Nothing Then
                            Try
                                rtns = .invoke4(bt)
                            Catch ex As Exception
                                raiseError("cannot invoke to registered function " + getInvokeMethodName(.invoke4) _
                                           + ", " + ex.Message, errorHandle.errorType.warning)
                            End Try
                        Else
                            assert(False, "should not have a function with no invoke available, funcname " + funcname)
                        End If
                        If Not rtns Is Nothing Then
                            Dim i As Int64
                            For i = 0 To rtns.Length() - 1
                                If Not rtns(i) Is Nothing Then
                                    rtnData.insert(rtns(i).tobytes())
                                Else
                                    raiseError(errorHandle.errorType.warning, _
                                               "not a valid return ", i, " in ", funcname)
                                End If
                            Next
                        Else
                            raiseError("do not get valid return from invoke function " + getInvokeMethodName(.invoke) _
                                       , errorHandle.errorType.warning)
                        End If
                    End If
                End With
            End If
        End If

        If Not handle.return(rtnData) Then
            raiseError("cannot reply to client " + handle.remoteIPEndPoint().ToString, errorHandle.errorType.warning)
        End If
    End Sub

    Private Shared Function unittestInvoke(ByVal arg As bytesString) As bytesInt32
        Dim rtn As bytesInt32 = Nothing
        If convertor.toint32(arg, rtn) Then
            Return rtn
        Else
            Return rnd(0, strlen(arg), True)
        End If
    End Function

    Public Shared Sub unittest()
        Dim rcserver As rcserve = Nothing
        Try
            raiseError("********** rcserve unitTest **********", errorHandle.errorType.application)

            rcserver = New rcserve()
            rcserver.registerInvoke(Of bytesString)(share.unittest.unittestInvokeName, AddressOf unittestInvoke)
            rcserver.start(share.unittest.port)
            While rcserver.health() AndAlso Not consoleExtSignal()
                sleep()
            End While
            rcserver.stop()

            raiseError("********** finish unitTest ***********", errorHandle.errorType.application)
        Finally
            'for remotecall performance test, the thread will be killed after test finished.
            If Not rcserver Is Nothing AndAlso rcserver.health() Then
                rcserver.stop(0)
                'immediately stop server, as tcp connections cannot be released before app stops.
            End If

            raiseError("********** finish unitTest ***********", errorHandle.errorType.application)
        End Try
    End Sub
End Class
